package com.googlecode.microanalyst.designer.model.commands;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;

import com.googlecode.microanalyst.designer.i18n.DesignerMessages;
import com.googlecode.microanalyst.designer.model.AnalysisDiagram;
import com.googlecode.microanalyst.designer.model.AnalysisElement;
import com.googlecode.microanalyst.designer.model.AnalysisGuide;
import com.googlecode.microanalyst.designer.model.AnalysisConnection;
import com.googlecode.microanalyst.designer.model.AnalysisConnectionBendpoint;

public class CloneCommand extends Command {

	private List<AnalysisElement> parts, newTopLevelParts;
	private List<AnalysisConnection> newConnections;
	private AnalysisDiagram parent;
	private Map<AnalysisElement, Rectangle> bounds;
	private Map<AnalysisElement, Integer> indices;
	private Map<AnalysisElement, AnalysisElement> connectionPartMap;
	private ChangeGuideCommand vGuideCommand, hGuideCommand;
	private AnalysisGuide hGuide, vGuide;
	private int hAlignment, vAlignment;

	public CloneCommand() {
		super(DesignerMessages.CloneCommand_Label);
		parts = new LinkedList<AnalysisElement>();
	}

	public void addPart(AnalysisElement part, Rectangle newBounds) {
		parts.add(part);
		if (bounds == null) {
			bounds = new HashMap<AnalysisElement, Rectangle>();
		}
		bounds.put(part, newBounds);
	}

	public void addPart(AnalysisElement part, int index) {
		parts.add(part);
		if (indices == null) {
			indices = new HashMap<AnalysisElement, Integer>();
		}
		indices.put(part, new Integer(index));
	}

	protected void clonePart(AnalysisElement oldPart,
			AnalysisDiagram newParent, Rectangle newBounds,
			List<AnalysisConnection> newConnections,
			Map<AnalysisElement, AnalysisElement> connectionPartMap, int index) {
		AnalysisElement newPart = null;

		newPart = (AnalysisElement) oldPart.clone();

		if (oldPart instanceof AnalysisDiagram) {
			AnalysisDiagram diagram = (AnalysisDiagram) oldPart;
			List<AnalysisElement> children = diagram.getChildren();
			for (AnalysisElement element : children) {
				// for children they will not need new bounds
				clonePart(element, (AnalysisDiagram) newPart, null,
						newConnections, connectionPartMap, -1);

			}
			Iterator<AnalysisElement> i = children.iterator();
			while (i.hasNext()) {
				// for children they will not need new bounds
				clonePart(i.next(),
						(AnalysisDiagram) newPart, null, newConnections,
						connectionPartMap, -1);
			}
		}

		Iterator<AnalysisConnection> i = oldPart.getTargetConnections().iterator();
		while (i.hasNext()) {
			AnalysisConnection connection = i.next();
			AnalysisConnection newConnection = new AnalysisConnection();
			newConnection.setValues(connection);
			newConnection.setTarget(newPart);
			newConnection.setTargetTerminal(connection.getTargetTerminal());
			newConnection.setSourceTerminal(connection.getSourceTerminal());
			newConnection.setSource(connection.getSource());

			Iterator<AnalysisConnectionBendpoint> b = connection.getBendpoints()
					.iterator();
			List<AnalysisConnectionBendpoint> newBendPoints = new ArrayList<AnalysisConnectionBendpoint>();

			while (b.hasNext()) {
				AnalysisConnectionBendpoint bendPoint = b.next();
				AnalysisConnectionBendpoint newBendPoint = new AnalysisConnectionBendpoint();
				newBendPoint.setRelativeDimensions(
						bendPoint.getFirstRelativeDimension(),
						bendPoint.getSecondRelativeDimension());
				newBendPoint.setWeight(bendPoint.getWeight());
				newBendPoints.add(newBendPoint);
			}

			newConnection.setBendpoints(newBendPoints);
			newConnections.add(newConnection);
		}

		if (index < 0) {
			newParent.addChild(newPart);
		} else {
			newParent.addChild(newPart, index);
		}

		newPart.setSize(oldPart.getSize());

		if (newBounds != null) {
			newPart.setLocation(newBounds.getTopLeft());
		} else {
			newPart.setLocation(oldPart.getLocation());
		}

		// keep track of the new parts so we can delete them in undo
		// keep track of the oldpart -> newpart map so that we can properly
		// attach
		// all connections.
		if (newParent == parent)
			newTopLevelParts.add(newPart);
		connectionPartMap.put(oldPart, newPart);
	}

	public void execute() {
		connectionPartMap = new HashMap<AnalysisElement, AnalysisElement>();
		newConnections = new LinkedList<AnalysisConnection>();
		newTopLevelParts = new LinkedList<AnalysisElement>();

		Iterator<AnalysisElement> i = parts.iterator();

		AnalysisElement part = null;
		while (i.hasNext()) {
			part = i.next();
			if (bounds != null && bounds.containsKey(part)) {
				clonePart(part, parent, bounds.get(part),
						newConnections, connectionPartMap, -1);
			} else if (indices != null && indices.containsKey(part)) {
				clonePart(part, parent, null, newConnections,
						connectionPartMap,
						(indices.get(part)).intValue());
			} else {
				clonePart(part, parent, null, newConnections,
						connectionPartMap, -1);
			}
		}

		// go through and set the source of each connection to the proper
		// source.
		Iterator<AnalysisConnection> c = newConnections.iterator();

		while (c.hasNext()) {
			AnalysisConnection conn = c.next();
			AnalysisElement source = conn.getSource();
			if (connectionPartMap.containsKey(source)) {
				conn.setSource(connectionPartMap.get(source));
				conn.attachSource();
				conn.attachTarget();
			}
		}

		if (hGuide != null) {
			hGuideCommand = new ChangeGuideCommand(connectionPartMap.get(parts
					.get(0)), true);
			hGuideCommand.setNewGuide(hGuide, hAlignment);
			hGuideCommand.execute();
		}

		if (vGuide != null) {
			vGuideCommand = new ChangeGuideCommand(connectionPartMap.get(parts
					.get(0)), false);
			vGuideCommand.setNewGuide(vGuide, vAlignment);
			vGuideCommand.execute();
		}
	}

	public void setParent(AnalysisDiagram parent) {
		this.parent = parent;
	}

	public void redo() {
		for (Iterator<AnalysisElement> iter = newTopLevelParts.iterator(); iter
				.hasNext();)
			parent.addChild(iter.next());
		for (Iterator<AnalysisConnection> iter = newConnections.iterator(); iter
				.hasNext();) {
			AnalysisConnection conn = iter.next();
			AnalysisElement source = conn.getSource();
			if (connectionPartMap.containsKey(source)) {
				conn.setSource(connectionPartMap.get(source));
				conn.attachSource();
				conn.attachTarget();
			}
		}
		if (hGuideCommand != null)
			hGuideCommand.redo();
		if (vGuideCommand != null)
			vGuideCommand.redo();
	}

	public void setGuide(AnalysisGuide guide, int alignment,
			boolean isHorizontal) {
		if (isHorizontal) {
			hGuide = guide;
			hAlignment = alignment;
		} else {
			vGuide = guide;
			vAlignment = alignment;
		}
	}

	public void undo() {
		if (hGuideCommand != null)
			hGuideCommand.undo();
		if (vGuideCommand != null)
			vGuideCommand.undo();
		for (Iterator<AnalysisElement> iter = newTopLevelParts.iterator(); iter
				.hasNext();)
			parent.removeChild(iter.next());
	}

}
